﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;


namespace KeanuMvc.Reference {

    public class Logger {

        #region Fields
        public enum LogSeverity { ERROR, WARNING, INFORMATION };
        private StreamWriter swLogFile;
        private string strLogFileName;
        private string strPath;
        private string strAbsolutePath;
        private StringBuilder strbMsg = new StringBuilder();
        private int iMaxFileSizeMB;
        private bool bWrapErrorRecoveryAttemptedOnce;
        private bool bLogErrorRecoveryAttemptedOnce;
        private const int DEFAULT_MAX_LOG_SIZE_MB = 20;
        #endregion

        public Logger() {
            bWrapErrorRecoveryAttemptedOnce = false;
            bLogErrorRecoveryAttemptedOnce = false;
        }

        public void Initialize(string sPath, string sLogFileName) {
            Initialize(sPath, sLogFileName, DEFAULT_MAX_LOG_SIZE_MB);
        }

        public void Initialize(string sPath, string sLogFileName, int MaxFileSizeMB) {
            // This method shoud not handle any exceptions unless it is recoverable
            strPath = sPath;
            strLogFileName = sLogFileName;
            iMaxFileSizeMB = MaxFileSizeMB;

            strAbsolutePath = strPath + "\\" + strLogFileName;
            swLogFile = new StreamWriter(strAbsolutePath, true);
        }

        public void Terminate() {
            if (swLogFile != null) {
                swLogFile.Dispose();
            }
        }

        private void WrapLogFileOnMaxSize() {
            if (swLogFile.BaseStream.Length >= (iMaxFileSizeMB * 1024 * 1024)) {
                try {
                    swLogFile.Dispose();

                    FileStream fsLogFile = new FileStream(strAbsolutePath, FileMode.Create, FileAccess.Write);

                    if (fsLogFile != null) {
                        fsLogFile.Dispose();
                        swLogFile = new StreamWriter(strAbsolutePath, true);
                    }
                } catch {
                    if (!bWrapErrorRecoveryAttemptedOnce) {
                        bWrapErrorRecoveryAttemptedOnce = true;
                        RepairFile();
                    } else {
                        throw;
                    }
                }
            }
        }

        private void RepairFile() {
            // This method shoud not handle any exceptions unless it is recoverable

            StringBuilder sbDestFile = new StringBuilder();

            sbDestFile.AppendFormat("{0}\\{1}_{2}", strPath, DateTime.Now.ToString("yyyyMMddHHmmsss"), strLogFileName);

            if (swLogFile != null) {
                swLogFile.Dispose();
                File.Move(strAbsolutePath, sbDestFile.ToString());
                swLogFile = new StreamWriter(strAbsolutePath, true);
            }
        }

        public void LogInformation(string sMessage) {
            lock (strbMsg) {
                LogMessage(LogSeverity.INFORMATION, sMessage);
            }
        }

        public void LogInformation(string sClass, string sMethod, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}:{2}", sClass, sMethod, sMessage);

                LogMessage(LogSeverity.INFORMATION, strbMsg.ToString());
            }
        }

        public void LogInformation(int iErrorCode, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}", iErrorCode.ToString(), sMessage);

                LogMessage(LogSeverity.INFORMATION, strbMsg.ToString());
            }
        }

        public void LogWarning(string sMessage) {
            lock (strbMsg) {
                LogMessage(LogSeverity.WARNING, sMessage);
            }
        }

        public void LogWarning(string sClass, string sMethod, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}:{2}", sClass, sMethod, sMessage);

                LogMessage(LogSeverity.WARNING, strbMsg.ToString());
            }
        }

        public void LogWarning(int iErrorCode, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}", iErrorCode.ToString(), sMessage);

                LogMessage(LogSeverity.WARNING, strbMsg.ToString());
            }
        }

        public void LogError(string sMessage) {
            lock (strbMsg) {
                LogMessage(LogSeverity.ERROR, sMessage);
            }
        }

        public void LogError(string sClass, string sMethod, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}:{2}", sClass, sMethod, sMessage);

                LogMessage(LogSeverity.ERROR, strbMsg.ToString());
            }
        }

        public void LogError(int iErrorCode, string sMessage) {
            lock (strbMsg) {
                strbMsg.Remove(0, strbMsg.Length);
                strbMsg.AppendFormat("{0}:{1}", iErrorCode.ToString(), sMessage);

                LogMessage(LogSeverity.ERROR, strbMsg.ToString());
            }
        }

        private void LogMessage(LogSeverity logSeverity, string sMessage) {
            string sSeverity = "";
            // This is the ultimate LogMessage method that will be invoked finally by all other overloads
            switch (logSeverity) {
                case LogSeverity.ERROR:
                    sSeverity = "Error";
                    break;
                case LogSeverity.INFORMATION:
                    sSeverity = "Information";
                    break;
                case LogSeverity.WARNING:
                    sSeverity = "Warning";
                    break;
            }

            strbMsg.Remove(0, strbMsg.Length);
            strbMsg.AppendFormat("[{0}],[{1}],[{2}],{3}", DateTime.Now.ToString(), Thread.CurrentThread.ManagedThreadId.ToString(), sSeverity, sMessage);

            try {
                WrapLogFileOnMaxSize();
                swLogFile.WriteLine(strbMsg);
            } catch {
                if (!bLogErrorRecoveryAttemptedOnce) {
                    bLogErrorRecoveryAttemptedOnce = true;
                    RepairFile();
                } else {
                    throw;
                }
            }
        }
    }
}
